package com.process.on;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import sun.misc.Unsafe;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.LongAdder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author demo
 * @date 2020/12/28
 */
public class User {
    /**
     * http状态
     */
    @JSONField(serialize = false)
    private CloseableHttpClient httpClient;

    /**
     * 登录账号
     */
    private String name;

    /**
     * 登录密码
     */
    private String password;

    /**
     * 助手
     */
    @JSONField(serialize = false)
    private List<User> helpers;

    public User() {
    }

    public User(String name, String password) {
        this.name = name;
        this.password = password;
        login();
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public String getPassword() {
        return password;
    }

    /**
     * 登出关闭http状态保留
     */
    public void loginOut() {
        //设置登陆状态 = 0
        if (compareAndSwapLoginStatus(1,0)) {
            if (null != httpClient) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new RuntimeException("登出异常");
                }
            }
            if (null != helpers && helpers.size() > 0) {
                helpers.forEach(User::loginOut);
            }
        }
        //else 无需登出
    }

    /**
     * 设置帮助者
     *
     * @param helpers
     */
    public void setHelpers(List<User> helpers) {
        this.helpers = helpers;
        //帮助者登陆
        for (User helper : this.helpers) {
            helper.login();
        }
    }

    //-----------------操作

    /**
     * 计数器
     */
    @JSONField(serialize = false)
    final LongAdder count = new LongAdder();

    /**
     * 登陆状态 0:未登陆 1:已登陆 登陆 登出 CAS操作
     */
    @JSONField(serialize = false)
    private volatile int loginStatus = 0;

    /**
     * 私有非安全操作类
     */
    @JSONField(serialize = false)
    private static Unsafe U;
    @JSONField(serialize = false)
    private static Long LOGIN_STATUS_OFFSET;

    @JSONField(serialize = false)
    private static ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>();

    static {
        try {
            //初始化unsafe
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            U = (Unsafe) field.get("");
            Field loginStatus = User.class.getDeclaredField("loginStatus");
            loginStatus.setAccessible(true);
            //获取loginStatus偏移量
            LOGIN_STATUS_OFFSET = U.objectFieldOffset(loginStatus);
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    /**
     * 扩容
     */
    public void expand() {
        valid();
        threadLocal.set(new StringBuilder());
        //创建流程图
        String chartId = createFlow();

        //循环点赞
        for (User helper : this.helpers) {
            helper.doLike(chartId);
        }
        //自己点赞 正好5个 可以扩容1个免费文件
        doLike(chartId);

        //删除流程图
        deleteChart(chartId);
        //计数+1
        count.increment();
        threadLocal.get().append("已完成[" + count.sum() + "] 为流程图："+ chartId +" 点赞扩容操作完成\r\n");
        System.out.println(threadLocal.get().toString());
    }


    /**
     * 创建流程图 并返回对应的chartId
     *
     * @return chartId-流程图id
     */
    public String createFlow() {
        valid();
        try (CloseableHttpResponse response = formRequest(ProcessOnApi.CREATE_FLOW, null)) {
            String result = EntityUtils.toString(response.getEntity());
            String rule = "var chartId = \"([a-zA-Z0-9]*)\"";
            Pattern pattern = Pattern.compile(rule);
            Matcher matcher = pattern.matcher(result);
            if (matcher.find()) {
                //获取到正则匹配的值
                threadLocal.get().append("创建流程图成功=>");
                return matcher.group(1);
            } else {
                JSONObject object = JSONObject.parseObject(result);
                final String resultCode = "error";
                throw new RuntimeException("创建流程图出错，扩容失败=>" + object.getString(resultCode));
            }
        } catch (IOException e) {
            throw new RuntimeException("创建流程图出错啦！！！");
        }
    }
    /**
     * 登录操作
     */
    public void login() {
        if (null == this.name || "".equals(this.name)) {
            throw new RuntimeException("登陆名为空！");
        }
        if (null == this.password || "".equals(this.password)) {
            throw new RuntimeException("登陆密码为空！");
        }
        //初始化请求容器
        this.httpClient = HttpClients.custom().setRetryHandler((e, i, httpContext) -> false).build();
        //参数
        Map<String, Object> param = new HashMap<>();
        param.put("login_email", this.name);
        param.put("login_password", this.password);
        param.put("window", false);
        try (CloseableHttpResponse response = formRequest(ProcessOnApi.LOGIN, param)) {
            String result = EntityUtils.toString(response.getEntity());
            //响应成功码
            final String successCode = "success";
            //响应判断字段
            final String resultMsg = "msg";
            if (successCode.equals(JSONObject.parseObject(result).get(resultMsg))) {
                if (!compareAndSwapLoginStatus(0, 1)) {
                    System.out.printf("用户:账号【%s】或密码【%s】已经登陆过啦!!\r\n", this.name, this.password);
                    return;
                }
                System.out.printf("用户 %s 登录成功\r\n", this.name);
            } else {
                System.out.printf("用户:账号【%s】或密码【%s】错误,请检查!!\r\n", this.name, this.password);
                throw new RuntimeException();
            }
        } catch (IOException e) {
            //处理为null
            this.httpClient = null;
            throw new RuntimeException("登录出错啦！！！");
        }
    }

    /**
     * 为流程图点赞
     *
     * @param chartId 流程图id
     */
    public void doLike(String chartId) {
        //校验
        valid();
        Map<String, Object> param = new HashMap<>();
        param.put("chartId", chartId);
        //循环3次 成功跳出
        for (int i = 0; i < 2; i++) {
            try (CloseableHttpResponse response = formRequest(ProcessOnApi.DO_LIKE, param)) {
                //响应判断字段
                final String resultMsg = "result";
                if (JSONObject.parseObject(EntityUtils.toString(response.getEntity())).getBoolean(resultMsg)) {
                    threadLocal.get().append("用户 "+ this.name +" 点赞成功=>");
                    break;
                }
            } catch (IOException e) {
                throw new RuntimeException("为chartId[" + chartId + "]点赞失败！！！");
            }
        }
    }

    /**
     * 获取最大免费文件数
     *
     * @return
     */
    public String getMaxFreeChart() {
        valid();
        try (CloseableHttpResponse response = formRequest(ProcessOnApi.MAX_CHART, null)) {
            return EntityUtils.toString(response.getEntity());
        } catch (IOException e) {
            throw new RuntimeException("获取最大免费文件数失败！！！");
        }
    }


    /**
     * 删除流程图操作
     *
     * @param chartId 流程图id
     */
    public void deleteChart(String chartId) {
        valid();
        Map<String, Object> param = new HashMap<>();
        param.put("fileType", "chart");
        param.put("fileId", chartId);
        param.put("resource", "");
        try {
            doFormRequest(ProcessOnApi.MV_TRASH, param);
            doFormRequest(ProcessOnApi.DELETE, param);
            threadLocal.get().append("删除流程图成功=>");
        } catch (IOException e) {
            throw new RuntimeException("操作删除chartId[" + chartId + "]失败！！！");
        }
    }

    /**
     * post或get form表单请求
     *
     * @param url        路径
     * @param param      参数
     * @return
     * @throws IOException
     */
    private CloseableHttpResponse formRequest(String url, Map<String, Object> param) throws IOException {
        StringBuilder sb = new StringBuilder(url);
        if (null != param && param.size() > 0) {
            sb.append("?");
            param.forEach((k, v) -> sb.append(k + "=" + v + "&"));
        }
        HttpPost httpPost = new HttpPost(sb.toString());
        //设置超时配置 谨慎 因为可能超时无法删除文件造成问题
//        RequestConfig requestConfig =  RequestConfig.custom()
//                .setConnectionRequestTimeout(1000)
//                .setSocketTimeout(1000)
//                .setConnectTimeout(1000).build();
//        httpPost.setConfig(requestConfig);
        return httpClient.execute(httpPost);
    }

    /**
     * post或get form表单请求
     *
     * @param url        路径
     * @param param      参数
     * @return
     * @throws IOException
     */
    private void doFormRequest(String url, Map<String, Object> param) throws IOException {
        CloseableHttpResponse response = formRequest(url, param);
        if (null != response) {
            response.close();
        }
    }


    /**
     * CAS替换登陆状态
     *
     * @param oldStatus 原本的登陆状态
     * @param newStatus 新的登陆状态
     */
    private boolean compareAndSwapLoginStatus(Integer oldStatus, Integer newStatus) {
        return U.compareAndSwapInt(this, LOGIN_STATUS_OFFSET, oldStatus, newStatus);
    }


    /**
     * 校验操作
     * @return
     */
    private void valid() {
        if (!new Integer(1).equals(this.loginStatus)) {
            throw new RuntimeException("该用户未登陆！！！");
        }
    }
}
